﻿using GalaSoft.MvvmLight;
using System;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization;
using CsvHelper;
using System.Globalization;
using CsvHelper.Configuration;
using CsvHelper.Configuration.Attributes;

namespace RailYardManager
{
    public class MainFormViewModel : ViewModelBase
    {
        private Yard _curYard;   //Current yard
        public BindingList<YardItem> Yards { get; set; } = new BindingList<YardItem>();

        public void ScanDir(string dir)
        {
            DirectoryInfo root = new DirectoryInfo(dir);
            foreach (DirectoryInfo next in root.GetDirectories())
            {
                foreach (FileInfo file in next.GetFiles().Where(f=>f.Extension == ".yml"))
                {
                    try
                    {
                        using (StreamReader reader = new StreamReader(file.FullName))
                        {
                            string fileContent = reader.ReadToEnd();
                            // 处理文件内容
                            var deserializer = new DeserializerBuilder()
                                .WithNamingConvention(UnderscoredNamingConvention.Instance)
                                .Build();

                            Yard y = deserializer.Deserialize<Yard>(fileContent);
                            YardItem item = Yards.FirstOrDefault(i=>i.Id == y.Info.Id);
                            if(item != null)
                            {
                                item.SetYard(y);
                                item.FullName = file.FullName;
                            }
                            //else
                            //{
                            //    item = new YardItem(y);
                            //    Yards.Add(item);
                            //}
                            
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }
                }
            }
            OnScanFinished?.Invoke(null, null);
        }
        public void ReadFromCsv(string path)
        {
            try
            {
                using (var reader = new StreamReader(path))
                {
                    var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
                    {
                        HasHeaderRecord = true
                    };

                    using (var csv = new CsvReader(reader, csvConfig))
                    {
                        // read CSV file
                        var records = csv.GetRecords<dynamic>();

                        // output
                        foreach (var r in records)
                        {
                            //WriteLine($"{r.FirstName,-15}{r.LastName,-10}{r.JoinedDate,15}{r.Salary,15}{r.Active,5}");
                            YardItem item = new YardItem();
                            item.Id = r.ID;
                            item.Name = r.YardName;
                            item.Polygon_WKT = r.Polygon_WKT;
                            item.Main_Direction = r.Main_Direction;
                            Yards.Add(item);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public Yard CurYard
        {
            get => _curYard;
            set
            {
                if (Set(ref _curYard, value))
                {
                    OnCurrentYardChanged?.Invoke(this, null);
                    RaisePropertyChanged(nameof(CurYardName));
                }
            }
        }
        public string CurYardName
        {
            get
            {
                if (CurYard != null)
                {
                    return CurYard.Info.Name;
                }
                else
                {
                    return "Unselected";
                }
            }
        }

        public event EventHandler OnScanFinished;
        public event EventHandler OnCurrentYardChanged;
    }
    public class YardItem : ObservableObject
    {
        private string id;
        private string name;
        private string state;
        private string bts_yard_id;
        private int avgCarsPerYard;
        private int numOfTrack;
        private double _Latitude;
        private double _Longitude;
        private string fullName;

        public YardItem() { }
        public YardItem(Yard y)
        {
            Id = y.Info.Id;
            Name = y.Info.Name;
            State = y.Info.State;
            Bts_yard_id = y.Info.Bts_yard_id;
            AvgCarsPerYard = y.Info.AvgCarsPerYard;
            Latitude = y.Boundary.Average(p => p.Latitude);
            Longitude = y.Boundary.Average(p => p.Longitude);
            CheckYard(y);
        }

        public void SetYard(Yard y)
        {
            State = y.Info.State;
            Bts_yard_id = y.Info.Bts_yard_id;
            AvgCarsPerYard = y.Info.AvgCarsPerYard;
            Latitude = y.Boundary.Average(p => p.Latitude);
            Longitude = y.Boundary.Average(p => p.Longitude);
            CheckYard(y);
        }

        [Name("ID")]
        public string Id
        {
            get => id;
            set => Set(ref id, value);
        }
        [Name("YardName")]
        public string Name
        {
            get => name;
            set => Set(ref name, value);
        }
        [Ignore]
        public string State
        {
            get => state;
            set => Set(ref state, value);
        }
        [Ignore]
        public string Bts_yard_id
        {
            get => bts_yard_id;
            set => Set(ref bts_yard_id, value);
        }
        [Ignore]
        public int AvgCarsPerYard
        {
            get => avgCarsPerYard;
            set => Set(ref avgCarsPerYard, value);
        }
        [Ignore]
        public string FullName { get => fullName; set => fullName = value; }
        [Ignore]
        public double Latitude
        {
            get => _Latitude;
            set => Set(ref _Latitude, value);
        }
        [Ignore]
        public double Longitude
        {
            get => _Longitude;
            set => Set(ref _Longitude, value);
        }
        [Ignore]
        public int NumOfYards { get; set; }
        [Ignore]
        public bool HasClassificationYard { get; set; }
        [Ignore]
        public bool Finished { get; set; }
        public string Polygon_WKT { get; set; }
        public string Main_Direction { get; set; }
        [Name("num_siding_tracks_per_side")]
        public int NumOfClassificationTrack { get; set; }

        private void CheckYard(Yard yard)
        {
            if (yard is null)
            {
                throw new ArgumentNullException(nameof(yard));
            }

            bool isMultiYardStation = yard.Yards != null && yard.Yards.Count > 0;

            HasClassificationYard = false;
            NumOfClassificationTrack = 0;
            Finished = false;

            if (isMultiYardStation)
            {
                NumOfYards = yard.Yards.Count;
                foreach (Yard yy in yard.Yards)
                {
                    if(yy.Info.Name == "Classification Yard")
                    {
                        HasClassificationYard = true;
                        NumOfClassificationTrack += yy.Info.NumOfTrack;
                        break;
                    }
                }

                Finished = NumOfYards > 0 && HasClassificationYard && NumOfClassificationTrack > 0;
            }
            else
            {
                NumOfYards = 0;
                NumOfClassificationTrack = yard.Info.NumOfTrack;
                Finished = NumOfClassificationTrack > 0;
            }
        }
    }

    //View Model Objects
    public class Yard : ObservableObject
    {
        public YardInfo Info { get; set; } = new YardInfo();
        public BindingList<YPoint> Points { get; set; } = new BindingList<YPoint>();
        public BindingList<Yard> Yards { get; set; } = new BindingList<Yard>();
        public BindingList<Connection> Connections { get; set; } = new BindingList<Connection>();
        public BindingList<BoundaryPoint> Boundary { get; set; } = new BindingList<BoundaryPoint>();

        public YPoint GetPointById(int id)
        {
            var p = this.Points.FirstOrDefault(i => i.Id == id);
            if (p == null)
            {
                foreach (Yard sub in this.Yards)
                {
                    p = sub.GetPointById(id);
                    if (p != null) break;
                }
            }
            return p;
        }
        public static int MaxPointId(Yard yard)
        {
            int cur = 0;
            foreach (var p in yard.Points)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxPointId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }
        public static int MaxBoundaryPointId(Yard yard)
        {
            int cur = 100;
            foreach (var p in yard.Boundary)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxBoundaryPointId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }
        public static int MaxConnectionId(Yard yard)
        {
            int cur = 10000;
            foreach (var p in yard.Connections)
            {
                if (cur < p.Id)
                {
                    cur = p.Id;
                }
            }
            foreach (var y in yard.Yards)
            {
                int temp = MaxConnectionId(y);
                if (cur < temp)
                {
                    cur = temp;
                }
            }
            return cur;
        }

        public void SetEventHandler()
        {
            this.Info.PropertyChanged += Info_PropertyChanged;
            this.Points.ListChanged += Points_ListChanged;
            this.Connections.ListChanged += Connections_ListChanged;
            this.Boundary.ListChanged += Boundary_ListChanged;
            this.Yards.ListChanged += Yards_ListChanged;
        }

        private void Info_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            YardInfoEventArgs args = new YardInfoEventArgs();
            args.Yard = this;
            if (e.PropertyName == nameof(YardInfo.Name))
            {
                OnNameChanged?.Invoke(this, args);
            }
            else if (e.PropertyName == nameof(YardInfo.Id))
            {
                OnIdChanged?.Invoke(this, args);
            }
        }
        private void Points_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Point = Points[e.NewIndex];
                OnPointAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Point = Points[e.NewIndex];
                OnPointChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                PointEventArgs args = new PointEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnPointDeleted?.Invoke(sender, args);
            }
        }
        private void Connections_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Connection = Connections[e.NewIndex];
                OnConnectionAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Connection = Connections[e.NewIndex];
                OnConnectionChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                ConnectionEventArgs args = new ConnectionEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnConnectionDeleted?.Invoke(sender, args);
            }
        }
        private void Boundary_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Boundary = Boundary[e.NewIndex];
                OnBoundaryPointAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemChanged)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.Boundary = Boundary[e.NewIndex];
                OnBoundaryPointChanged?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                BoundaryEventArgs args = new BoundaryEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnBoundaryPointDeleted?.Invoke(sender, args);
            }
        }
        private void Yards_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (e.ListChangedType == ListChangedType.ItemAdded)
            {
                YardItemEventArgs args = new YardItemEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                args.SubYard = Yards[e.NewIndex];
                OnSubYardAdded?.Invoke(sender, args);
            }
            else if (e.ListChangedType == ListChangedType.ItemDeleted)
            {
                YardItemEventArgs args = new YardItemEventArgs();
                args.Yard = this;
                args.Index = e.NewIndex;
                OnSubYardDeleted?.Invoke(sender, args);
            }
        }


        public event EventHandler<YardInfoEventArgs> OnNameChanged;
        public event EventHandler<YardInfoEventArgs> OnIdChanged;
        public event EventHandler<PointEventArgs> OnPointAdded;
        public event EventHandler<PointEventArgs> OnPointDeleted;
        public event EventHandler<PointEventArgs> OnPointChanged;
        public event EventHandler<ConnectionEventArgs> OnConnectionAdded;
        public event EventHandler<ConnectionEventArgs> OnConnectionDeleted;
        public event EventHandler<ConnectionEventArgs> OnConnectionChanged;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointAdded;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointDeleted;
        public event EventHandler<BoundaryEventArgs> OnBoundaryPointChanged;
        public event EventHandler<YardItemEventArgs> OnSubYardAdded;
        public event EventHandler<YardItemEventArgs> OnSubYardDeleted;
    }
    public class BoundaryPoint : ObservableObject
    {
        private int id;
        private double latitude;
        private double longitude;

        public int Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public double Latitude
        {
            get => latitude;
            set => Set(ref latitude, value);
        }
        public double Longitude
        {
            get => longitude;
            set => Set(ref longitude, value);
        }

        public override string ToString()
        {
            return $"Point_{Id}";
        }
    }
    public class YardInfo : ObservableObject
    {
        private string id;
        private string name;
        private string state;
        private string bts_yard_id;
        private int avgCarsPerYard;
        private int numOfTrack;

        public string Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public string Name
        {
            get => name;
            set => Set(ref name, value);
        }
        public string State
        {
            get => state;
            set => Set(ref state, value);
        }
        public string Bts_yard_id
        {
            get => bts_yard_id;
            set => Set(ref bts_yard_id, value);
        }
        public int AvgCarsPerYard
        {
            get => avgCarsPerYard;
            set => Set(ref avgCarsPerYard, value);
        }
        public int NumOfTrack
        {
            get => numOfTrack;
            set => Set(ref numOfTrack, value);
        }
    }
    public class YPoint : ObservableObject
    {
        private int _Id;
        private YPointType _Type;
        private double _Latitude;
        private double _Longitude;

        public int Id
        {
            get => _Id;
            set => Set(ref _Id, value);
        }
        public YPointType Type
        {
            get => _Type;
            set => Set(ref _Type, value);
        }
        public double Latitude
        {
            get => _Latitude;
            set => Set(ref _Latitude, value);
        }
        public double Longitude
        {
            get => _Longitude;
            set => Set(ref _Longitude, value);
        }

        public override string ToString()
        {
            return $"Point_{Id}: {Type.ToString()}";
        }
    }
    public class Connection : ObservableObject
    {
        private int id;
        private YConnectionType type;
        private int fromPointId;
        private int toPointId;

        public int Id
        {
            get => id;
            set => Set(ref id, value);
        }
        public YConnectionType Type
        {
            get => type;
            set => Set(ref type, value);
        }
        public int FromPointId
        {
            get => fromPointId;
            set => Set(ref fromPointId, value);
        }
        public int ToPointId
        {
            get => toPointId;
            set => Set(ref toPointId, value);
        }

        public override string ToString()
        {
            return $"Connect_{Id}: {Type.ToString()}";
        }
    }

    //Events
    public class YardInfoEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
    }
    public class PointEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public YPoint Point { get; set; }
        public int Index { get; set; }
    }
    public class ConnectionEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public Connection Connection { get; set; }
        public int Index { get; set; }
    }
    public class BoundaryEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public BoundaryPoint Boundary { get; set; }
        public int Index { get; set; }
    }
    public class YardItemEventArgs : EventArgs
    {
        public Yard Yard { get; set; }
        public Yard SubYard { get; set; }
        public int Index { get; set; }
    }

    //Enums
    public enum YConnectionType
    {
        bridge,
        inbound_line
    }
    public enum YPointType
    {
        gate_in,
        gate_out,
        inbound_point,
        outbound_point,
        hump
    }
}
